package se.lnu.axez.shaders;

import java.util.HashMap;
import java.util.Map;

import se.lnu.axez.AxezContext;
import se.lnu.axez.Utils;
import android.opengl.GLES20;
import android.util.Log;

public final class ShaderProgram {
	/**
	 * Value used to indicate that uniform or attribute was not found in the program.
	 */
	public static final int INVALID_ID = -1;

	private static final String TAG = ShaderProgram.class.getSimpleName();

	private final VertexShader vertexShader;
	private final FragmentShader fragmentShader;
	private final Map<String, Integer> attributeIDs = new HashMap<String, Integer>();
	private final Map<String, Integer> uniformIDs = new HashMap<String, Integer>();
	private int programID;
	private int featureSet;
	private boolean isLoaded;
	
	ShaderProgram(VertexShader vertexShader, FragmentShader fragmentShader) {
		this.vertexShader = vertexShader;
		this.fragmentShader = fragmentShader;
	}

	void load() {
		this.programID = GLES20.glCreateProgram();
        if (this.programID == 0) {
        	String msg = String.format("Could not create program.");
        	Log.e(TAG, msg);
        	throw new RuntimeException(msg);
        }
		
		GLES20.glAttachShader(this.programID, this.vertexShader.getShaderID());
		Utils.checkGlError(TAG, "glAttachShader");
		GLES20.glAttachShader(this.programID, this.fragmentShader.getShaderID());
		Utils.checkGlError(TAG, "glAttachShader");

        GLES20.glLinkProgram(this.programID);
        
        // check linking status
        int[] linkStatus = new int[1];
        GLES20.glGetProgramiv(this.programID, GLES20.GL_LINK_STATUS, linkStatus, 0);
        if (linkStatus[0] != GLES20.GL_TRUE) {
        	String msg = "Could not link program.";
            Log.e(TAG, msg);
            Log.e(TAG, GLES20.glGetProgramInfoLog(this.programID));
            GLES20.glDeleteProgram(this.programID);
            throw new RuntimeException(msg);
        }
        
        this.featureSet = 
        		this.vertexShader.getRequiredShaderFeatures() 
        		| this.fragmentShader.getRequiredShaderFeatures();

        this.isLoaded = true;
	}
	
	public boolean isFeatureRequested(int feature) {
		return (this.featureSet & feature) == feature;
	}
	
	public int getAttributeID(String name) {
		if (this.attributeIDs.containsKey(name)) {
			return this.attributeIDs.get(name);
		}
		
		int attrID = GLES20.glGetAttribLocation(this.programID, name);
		Utils.checkGlError(TAG, "glGetAttribLocation " + name);
		if (attrID == INVALID_ID) {
			Log.w(TAG, "Could not get attrib location for " + name);
			return INVALID_ID;
//			throw new RuntimeException("Could not get attrib location for " + name);
		}
		
		this.attributeIDs.put(name, attrID);
		return attrID;
	}
	
	public int getUniformID(String name) {
		if (this.uniformIDs.containsKey(name)) {
			return this.uniformIDs.get(name);
		}
		
        int uniID = GLES20.glGetUniformLocation(this.programID, name);
        Utils.checkGlError(TAG, "glGetUniformLocation " + name);
        if (uniID == -1) {
        	Log.w(TAG, "Could not get uniform location for " + name);
        	return INVALID_ID;
//            throw new RuntimeException("Could not get uniform location for " + name);
        }		
		
        this.uniformIDs.put(name, uniID);
        return uniID;
	}

	public void use(AxezContext context) {
		if (!this.isLoaded) {
			this.load();
		}
		
		GLES20.glUseProgram(this.programID);
		Utils.checkGlError(TAG, "glUseProgram");
		
		context.getRenderMatrices().applyMatrices(this);
	}
	
	public void dispose(){
		// TODO: add implementation
	}
}
